package gochecker

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"sync"
)

const (
	//field error format
	fieldErrFmt = "field : '%s' is '%s'"
)

// CheckerErrors gochecker maybe returns a list of error fields
type CheckerErrors []*fieldError

func (c *CheckerErrors) Error() string {
	buff := bytes.NewBufferString("")
	for _, v := range *c {
		buff.WriteString(v.Error())
		buff.WriteString("\n")
	}
	return strings.TrimSpace(buff.String())
}

// InvalidCheckerError this error is returned when the incoming parameter is invalid
type InvalidCheckerError struct {
	Actual reflect.Type
}

func (e *InvalidCheckerError) Error() string {
	if e.Actual == nil {
		return "checker : (nil pointer)"
	}
	return "checker : (expect elem kind of struct/slice actual elem type " + e.Actual.String() + ")"
}

type fieldError struct {
	//structure field name, used ./[N] split parent-child/slices
	fieldName string
	//null, empty, zero
	err string
}

func (c *fieldError) Error() string {
	return fmt.Sprintf(fieldErrFmt, c.fieldName, c.err)
}

// Option gochecker option
type Option struct {
	//traverse the internal data validity of slice
	SliceElem bool
	//record field name when label name is ""
	TagName string
	//customize root node name, nil use type name
	RootName *string
}

var (
	defaultOpt = &Option{
		SliceElem: false,
		TagName:   "json",
	}
)

// Struct check the legal line of the structure data according to the incoming opt
// v struct pointer
func Struct(v interface{}) error {
	return StructWithOpt(v, defaultOpt)
}

// StructWithOpt check the legal line of the structure data according to the incoming opt
// v struct pointer
// opt gochecker option
func StructWithOpt(v interface{}, opt *Option) error {
	if opt == nil {
		opt = defaultOpt
	}
	rv, err := getInterfaceElem(v)
	if err != nil {
		return err
	}
	if rv.Kind() != reflect.Struct {
		return &InvalidCheckerError{Actual: rv.Type()}
	}
	t := newT()
	t.getOptFlag(opt)
	fn := rv.Type().Name()
	if opt.RootName != nil {
		fn = *opt.RootName
	}
	t.structure(fn, rv)
	return t.getCheckerErrors()
}

//getValueElem loop acquisition pointer or interface中元素
func getValueElem(v *reflect.Value) error {
	//reflect.ValueOf((interface{})(nil))
	if v.Kind() == reflect.Invalid {
		return &InvalidCheckerError{}
	}
	for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
		if !v.IsNil() {
			*v = v.Elem()
			continue
		}
		return &InvalidCheckerError{}
	}
	return nil
}

//getInterfaceElem solution pointer
func getInterfaceElem(v interface{}) (*reflect.Value, error) {
	if v == nil {
		return nil, &InvalidCheckerError{}
	}

	rv := reflect.ValueOf(v)

	for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
		if !rv.IsNil() {
			rv = rv.Elem()
			continue
		}
		return nil, &InvalidCheckerError{}

	}
	return &rv, nil
}

// reduce gc frequency through sync.pool
var free = sync.Pool{
	New: func() interface{} { return new(t) },
}

const (
	//notNull flag
	nf = uint8(1 << iota)
	//notEmpty flag
	ef
	//notZero flag
	zf
	//slice checker flag
	sf
)

//struct tag and tag value
const (
	nn  = "notNull"
	ne  = "notEmpty"
	nz  = "notZero"
	tag = "checker"

	//default slice
	obs = "[]"
)

const (
	in = "null"
	ie = "empty"
	iz = "zero"
)

// t save multilevel field name buffer or error list
type t struct {
	//flag 8 bit storage logo
	f uint8
	//check error list
	el []*fieldError
	//current index of el
	ce uint8
	//flied buffer
	fb *bytes.Buffer
	//tag name
	tn string
}

// newT get t from sync.pool
func newT() *t {
	t := free.Get().(*t)
	t.f = 0
	//reuse this memory use t.ce record its subscript
	if t.el == nil {
		t.el = make([]*fieldError, 0, 2)
	}
	//like t.el
	if t.el != nil {
		t.fb = bytes.NewBuffer(make([]byte, 0, 32))
	} else {
		t.fb.Reset()
	}
	t.ce = 0
	return t
}

//appendErr append error to t.el
func (t *t) appendErr(err *fieldError) {
	if int(t.ce) >= len(t.el) {
		t.el = append(t.el, err)
	} else {
		t.el[t.ce] = err
	}
	t.ce++
}

//getCheckerTag get the specified field name and its validation id
func getCheckerTag(f *reflect.StructField) uint8 {
	fl := uint8(0)
	if v := f.Tag.Get(tag); v != "" {
		if v == nn {
			fl = nf
		} else if v == ne {
			fl = ef
		} else if v == nz {
			fl = zf
		}
		return fl
	}
	return fl
}

// getStructFieldName using sync.pool may cause the memory
// of t.el to be reused. copy the errors and return
func (t *t) getStructFieldName(f *reflect.StructField) string {
	if t.tn == "" {
		return f.Name
	}
	v := f.Tag.Get(t.tn)
	if v != "" {
		for i, it := range v {
			if it == ',' {
				return v[:i]
			}
		}
		return v
	}
	return f.Name
}

//fieldError get StructField by normal field
func (t *t) fieldError(f *reflect.StructField, err string) {
	fn := t.getStructFieldName(f)
	t.pushFieldName(fn, false, 0)
	defer t.popFieldName(fn, false, 0)
	fe := &fieldError{
		fieldName: t.getFieldName(),
		err:       err,
	}
	t.appendErr(fe)
}

//sliceError get StructField by slice field
func (t *t) sliceError(err string) {
	fe := &fieldError{
		fieldName: t.getFieldName(),
		err:       err,
	}
	t.appendErr(fe)
}

//pushFieldName push field name to t.fb
func (t *t) pushFieldName(fn string, isSlc bool, i int) {
	if t.fb.Len() != 0 && fn != "" {
		t.fb.WriteByte('.')
	}
	t.fb.WriteString(fn)
	if isSlc {
		t.fb.WriteString("[" + strconv.Itoa(i) + "]")
	}
}

//popFieldName pop field name from t.fb
func (t *t) popFieldName(fn string, isSlc bool, i int) {
	size := len(fn)
	if isSlc {
		size += len("[" + strconv.Itoa(i) + "]")
	}
	t.fb.Truncate(t.fb.Len() - size)
	if t.fb.Len() > 0 && fn != "" {
		t.fb.Truncate(t.fb.Len() - 1)
	}
}

//popFieldName get field name from t.fb
func (t *t) getFieldName() string {
	return t.fb.String()
}

// getCheckerErrors using sync.pool may cause the memory
// of t.el to be reused. copy the errors and return
func (t *t) getCheckerErrors() *CheckerErrors {
	if t.ce == 0 {
		return nil
	}
	rlt := make([]*fieldError, len(t.el[:t.ce]))
	copy(rlt, t.el)
	return (*CheckerErrors)(&rlt)
}

//structure Check the legitimacy of the structure and its
//internal field data against the flag, and log errors
func (t *t) structure(fn string, v *reflect.Value) {

	t.pushFieldName(fn, false, 0)
	defer t.popFieldName(fn, false, 0)

	for i := 0; i < v.NumField(); i++ {
		tf := v.Type().Field(i)
		if !tf.IsExported() {
			continue
		}

		f := getCheckerTag(&tf)

		//tmp value
		tv := v.Field(i)
		err := getValueElem(&tv)
		if err != nil && (f&zf != 0 || f&nf != 0 || f&ef != 0) {
			t.fieldError(&tf, in)
			continue
		}

		if tv.Kind() == reflect.Struct {
			t.structure(t.getStructFieldName(&tf), &tv)
			continue
		}

		if f != 0 {

			if f&zf != 0 {
				if tv.IsZero() {
					t.fieldError(&tf, iz)
				}
			} else if f&ef != 0 {
				switch tv.Kind() {
				case reflect.String, reflect.Slice, reflect.Map:
					if tv.Len() == 0 {
						t.fieldError(&tf, ie)
					}
				}
			} else if f&nf != 0 {
				switch tv.Kind() {
				case reflect.Slice, reflect.Map, reflect.UnsafePointer, reflect.Interface:
					if tv.IsNil() {
						t.fieldError(&tf, in)
					}
				}
			}

		}

		if tv.Kind() == reflect.Slice && t.f&sf != 0 {
			t.slice(t.getStructFieldName(&tf), f, &tv)
		}
	}
}

//slice vheck the legitimacy of the slice and its
//internal field data against the flag, and log errors
func (t *t) slice(fn string, flag uint8, v *reflect.Value) {
	for i := 0; i < v.Len(); i++ {
		func() {
			t.pushFieldName(fn, true, i)
			defer t.popFieldName(fn, true, i)
			tv := v.Index(i)

			err := getValueElem(&tv)
			if err != nil && (t.f&zf != 0 || t.f&ef != 0 || t.f&nf != 0) {
				t.sliceError(in)
				return
			}

			if tv.Kind() == reflect.Slice {
				t.slice("", flag, &tv)
				return
			}

			if tv.Kind() == reflect.Struct {
				t.structure("", &tv)
				return
			}
		}()
	}
}

//getOptFlag Get the operation ID of opt and record it
func (t *t) getOptFlag(opt *Option) {
	if opt.SliceElem {
		t.f |= sf
	}
	t.tn = opt.TagName

}
